/* File: ConcreteOrganism.java
 * Purpose: An organism that exists in the simulation environment
 * Author: Team Karp
 * Date: 12/11/07
 * Revisions: 12/11/07, Adam Patterson - Initial Version
 *            [2007-12-20, Ryan J. Bovorasmy] Started implementing.
 *            [2007-12-20, Adam Patterson] Added 'Act'
 *            [2008-01-13, Adam Patterson] Added ExposedAttribute accessor and mutators
 *            [2008-01-13, Adam Patterson] Added ordinal retreiver and overloaded 
 *                              accessors and mutators for ordinals
 *            [2008-02-06, Adam Patterson] Completed an inital version of the act execution
 * Additional Requirements: 
 */
package protovitae.data;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * An organism that exists in the simulation environment
 * @version 1.0
 * @created 06-Dec-2007 16:19:09
 */
public class ConcreteOrganism implements Organism, MutableTreeNode {

    protected boolean alive = true; //Is Organism alive? If false, dead.
    protected ExposedAttribute[] attributes = new ExposedAttribute[ATTRIBUTE_TYPE.values().length];
    protected ArrayList<Gene> genome = new ArrayList<Gene>(); //Traits of this organism
    private Simulation parentSimulation; //the environment in which the organism exists
    protected Position position; //where the organism is in the simulated environment
    protected Position direction; // The direction the organism is facing.
    protected Color color; // The color of the organism.
    protected Color edibleFoodColor = Color.GREEN; //the color of this organism's food choice
    protected double edibleRange = .6; //the value (from 0 to 1) of edible foods around the edibleFoodColor
    private Organism.ACTION_TYPE currentAction = Organism.ACTION_TYPE.MOVE;
    protected static long id = 1;
    public long myId; //Was formerly final, changed for loading purposes.

    public String getGeneticInformation() {
        /*
         * Is all of this stuff used? 
         * Is the eat type the base and the range the allowed deviance 
         * from that range?
         */
        StringBuilder builder = new StringBuilder();
        builder.append(id);
        builder.append("|");
        builder.append("c#"); // Color Section.

        builder.append(padBytes(Integer.toHexString(color.getRed())));
        builder.append(padBytes(Integer.toHexString(color.getGreen())));
        builder.append(padBytes(Integer.toHexString(color.getBlue())));
        builder.append(",");
        builder.append("e#"); // Eat type section.
        builder.append(padBytes(Integer.toHexString(edibleFoodColor.getRed())));
        builder.append(padBytes(Integer.toHexString(edibleFoodColor.getGreen())));
        builder.append(padBytes(Integer.toHexString(edibleFoodColor.getBlue())));
//        builder.append(",");
//        builder.append("r#"); // Edible Range Section.
//        builder.append(padBytes(Long.toHexString((((Double)edibleRange).doubleToLongBits(edibleRange)))));

        // Include code for the actual genes in this organism.
        int i = 0;
        //System.out.println("This organism has "+genome.size()+" traits: "+this);
        while (i < genome.size()) {
            builder.append(",g#" + genome.get(i).getName());
            ++i;
        }
        
        char soh = 1;
        builder.append(soh);
        //System.out.println("Build genetic information: "+builder.toString());
        return builder.toString();
    }

    private String padBytes(String hexStringBuffer) {
        return (hexStringBuffer.length() % 2 != 0) ? "0" + hexStringBuffer : hexStringBuffer;
    }

    /**
     * 
     */
    public ConcreteOrganism(Simulation parentSim) {
        this.parentSimulation = parentSim;
        myId = id;
        //id++;
        for (int i = 0; i < ATTRIBUTE_TYPE.values().length; i++) {
            attributes[i] = new ExposedAttribute(2, 5, true);
        }

        init(new ArrayList<Gene>(), new Position(15, 75, -40), new Position(1, -1, 0), Color.getHSBColor((float) Math.random(), 1.0f, (float) (Math.random() / 2.0) + 0.5f));
    }

    public ConcreteOrganism(String geneticInformation, Simulation parentSim) {
        this.parentSimulation = parentSim;
        myId = -1;

        for (int i = 0; i < ATTRIBUTE_TYPE.values().length; i++) {
            attributes[i] = new ExposedAttribute(2, 5, true);
        }

        String mySplitById[] = geneticInformation.split("\\|");       // Transient.

        String myComponents[] = mySplitById[1].split(","); // list of k#value pairs.

       // System.out.println("GENTIC INFOMRATION IN CONSTUCTION: " + geneticInformation);

        for (String component : myComponents) {
            try {
                String keyValuePairs[] = component.split("\\#");
                String key = keyValuePairs[0];
                String value = keyValuePairs[1];
                /*
                 * Mandatory fields.
                 */
                if ("c".equals(key)) {
                    this.color = Color.decode("0x" + value.trim());
                    init(new ArrayList<Gene>(), new Position(15, 75, -40), new Position(1, -1, 0), this.color);
                } else if ("e".equals(key)) {
                    this.edibleFoodColor = Color.decode("0x" + value.trim());
                } else if ("r".equals(key)) {
                // This value is currently static, don't do anything yet. 
                } else if("g".equals(key)){
                    Gene gene = parentSimulation.getMasterlist().getGene(value);
                    
                    if (gene != null) {
                        this.addGene(gene);
                    }
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
            // This gene has no data. 
            }
        }

    // HACK: This adds the default gene back onto a child organism!!!!!
    //       Remove this once a master list can be populated to allow
    //       for correct reconstruction!!!             
//        Gene gene = parentSimulation.getMasterlist().getGene("New gene");
//        if(gene==null){
//            gene = new Gene();
//        }
//        this.addGene(gene);
    }

    /**
     * 
     * @param right
     */
    public ConcreteOrganism(ConcreteOrganism right, Simulation parentSim) {
        this.parentSimulation = parentSim;
        myId = id;
        //id++;
        for (int i = 0; i < ATTRIBUTE_TYPE.values().length; i++) {
            this.attributes[i] = new ExposedAttribute();
            this.attributes[i].setMax(right.getAttribute(i).getMax());
            this.attributes[i].setCurrent(right.getAttribute(i).getCurrent());
        }

        init(right.getGenome(), right.getPosition(), right.getDirection(), right.getColor());
    }

    /**
     * 
     * @param genome
     * @param position
     * @param color
     */
    private void init(ArrayList<Gene> genome, Position position, Position direction, Color color) {
        for (Gene g : genome) {
            this.genome.add(g);
        }
        this.setPosition(new Position(position.getX(), position.getY(), position.getZ())); //Was 0,0,0. Change to 0,0,0?
        setColor(color);
        this.setDirection(new Position(direction.getX(), direction.getY(), direction.getZ()));
        Color myColor = this.getColor();
        int red = myColor.getRed();
        int blue = myColor.getBlue();
        int green = myColor.getGreen();
        int totalColor = red + green + blue;

        setAttributeValue(Organism.ATTRIBUTE_TYPE.BRAWN, ((totalColor % 5) + 1) + (red + green) % 2); //Temporary code

        ExposedAttribute health = new ExposedAttribute();
        health.setMax((totalColor % 200 + 10) + (blue + red) % 15);
        health.setCurrent(health.getMax());
        this.setAttribute(Organism.ATTRIBUTE_TYPE.HEALTH.ordinal(), health);
        
        ExposedAttribute energy = new ExposedAttribute();
        energy.setMax((totalColor % 15 + 2) + (green + blue) % 10);
        energy.setCurrent(energy.getMax());
        this.setAttribute(Organism.ATTRIBUTE_TYPE.ACTION.ordinal(), energy);
        
        ExposedAttribute attack = new ExposedAttribute();
        attack.setMax((totalColor % 10 + 1) + (red + green) % 10);
        attack.setCurrent(attack.getMax());
        this.setAttribute(Organism.ATTRIBUTE_TYPE.BRAWN.ordinal(), attack);        
        checkLife();
    }

    /**
     * Adds a gene to the organism.
     * @param gene - characteristic of the organism
     */
    public void addGene(Gene gene) {
        this.genome.add(gene);
    }

    /**
     * Determines which action to take and takes it
     * @param parent - The simulation environemnt.
     */
    public void act(Simulation parent) {
        //rest 1+ enregy per round
        this.parentSimulation = parent;
        this.getAttribute(Organism.ATTRIBUTE_TYPE.ACTION).setCurrent(this.getAttribute(Organism.ATTRIBUTE_TYPE.ACTION).getCurrent() + 1);

        if (!checkLife()) {
            //Rot away
            setAttributeValue(ATTRIBUTE_TYPE.HEALTH,
                    getAttribute(ATTRIBUTE_TYPE.HEALTH).getCurrent() - 1);
            return; //dead
        }

        //TODO: If unable to reach target, move towards
        ArrayList<Organism.ACTION_TYPE> votes = new ArrayList<Organism.ACTION_TYPE>();
        ArrayList<ConcreteOrganism> attackTargets = new ArrayList<ConcreteOrganism>();
        ArrayList<ConcreteOrganism> mateTargets = new ArrayList<ConcreteOrganism>();
        ArrayList<ConcreteOrganism> eatTargets = new ArrayList<ConcreteOrganism>();

        ArrayList tempVoteInfo;
        ConcreteOrganism tempTarget;
        Organism.ACTION_TYPE tempVote;

        //total votes and targets
        for (Gene gene : genome) {
            tempVoteInfo = gene.act(parent, this);

            tempVote = (Organism.ACTION_TYPE) tempVoteInfo.get(0);
            tempTarget = (ConcreteOrganism) tempVoteInfo.get(1);

            votes.add(tempVote);
            if (tempVote == Organism.ACTION_TYPE.ATTACK) {
                attackTargets.add(tempTarget);
            } else if (tempVote == Organism.ACTION_TYPE.MATE) {
                mateTargets.add(tempTarget);
            } else if (tempVote == Organism.ACTION_TYPE.EAT) {
                eatTargets.add(tempTarget);
            }
        }

        //determine winning vote
        Iterator<Organism.ACTION_TYPE> itr = votes.iterator();
        ArrayList<Integer> totals = new ArrayList<Integer>(5);

        int attTot = 0;
        int matTot = 0;
        int eatTot = 0;
        int danTot = 0;
        int movTot = 0;

        while (itr.hasNext()) {
            switch (itr.next()) {
                case ATTACK:
                    attTot++;
                    break;
                case MATE:
                    matTot++;
                    break;
                case EAT:
                    eatTot++;
                    break;
                case DANCE:
                    danTot++;
                    break;
                case MOVE:
                    movTot++;
                    break;
                default:
                    break;
            }
        }

        totals.add(attTot);
        totals.add(matTot);
        totals.add(eatTot);
        totals.add(danTot);
        totals.add(movTot);

        //determine most voted action
        int maxInd = 0;
        for (int i = 0; i < totals.size(); ++i) {
            if (totals.get(i) > totals.get(maxInd)) {
                maxInd = i;
            }
        }

        switch (maxInd) {
            case 0:
                //attack
                attack(findTarget(attackTargets));
                break;
            case 1:
                //mate
                mate(findTarget(mateTargets));
                break;
            case 2:
                //eat
                eat(findTarget(eatTargets));
                break;
            case 3:
                //dance
                dance(parent, this);
                break;
            case 4:
                //move
                move(parent, this);
                break;
            default:
                break;
        }
        checkLife();
    }

    /**
     * Determines the most commonly occuring organism in a list
     * @param targetList
     * @return
     */
    private ConcreteOrganism findTarget(ArrayList<ConcreteOrganism> targetList) {
        //determine winning vote
        Iterator<ConcreteOrganism> itr = targetList.iterator();
        HashMap<ConcreteOrganism, Integer> votes = new HashMap<ConcreteOrganism, Integer>();
        ConcreteOrganism tempOrg = null;
        int currentVotes = 0;

        //add up votes
        while (itr.hasNext()) {
            currentVotes = 0;
            tempOrg = itr.next();
            if (votes.containsKey(tempOrg)) {
                currentVotes = votes.get(tempOrg).intValue();
                currentVotes++;
                votes.put(tempOrg, currentVotes);
            } else {
                votes.put(tempOrg, 1);
            }
        }

        //return 'winner'
        int mostVotes = 0;
        for (ConcreteOrganism test : votes.keySet()) {
            if (votes.get(test).intValue() > mostVotes) {
                mostVotes = votes.get(test).intValue();
                tempOrg = test;
            }
        }


        checkLife();
        System.out.println(this+" is targetting "+tempOrg);
        return tempOrg;
    }

    /**
     * Attacks another organism.
     * @param organism The organism to attack.
     */
    public void attack(Organism organism) {
        if(organism==null){
            return;
        }
//        if (this.getPosition().getXYDistance(((ConcreteOrganism) organism).position) > 50) {
//            this.move(parentSimulation, this); // Just move if my target isn't that close.
//            return;
//        }
        for (Gene gene : genome) {
            gene.attack(organism);
        }
        checkLife();
    }

    /**
     * Causes the organism to dance. Dancing attracts a mate, etc.
     * @param parent - The simulation environemnt.
     * @param organism
     */
    public void dance(Simulation parent, Organism organism) {
        for (Gene gene : genome) {
            gene.dance(parent, organism);
        }
        checkLife();
    }

    /**
     * Attempts to eat another organism.
     * @param organism The organism to eat.
     */
    public void eat(Organism organism) {
        boolean canEat = canEat((ConcreteOrganism) organism);

        System.out.println(this+" eating: "+organism+" ? "+canEat);
        if (canEat) {
            for (Gene gene : genome) {
                gene.eat(organism);
            }
        }
        checkLife();
    }

    /**
     * Determines if this organism can eat another one
     * @param organism - possible food source
     * @return - true if organism is edible
     */
    public boolean canEat(ConcreteOrganism organism) {
        float[] favoriteFoodHSBColor = Color.RGBtoHSB(edibleFoodColor.getRed(), edibleFoodColor.getGreen(), edibleFoodColor.getBlue(), null);
        double maxHue = favoriteFoodHSBColor[0] + this.edibleRange / 2.0;
        double minHue = favoriteFoodHSBColor[0] - this.edibleRange / 2.0;
        double foodHue = organism.getHue();
        boolean canEat = false;

        //check if this food is edible
        //the different cases check the range on three circles (-1 to 0, 0 to 1, and 1 to 2) to account 
        //  for max and mins that would overflow the range
        if (foodHue <= maxHue && foodHue >= minHue) {
            canEat = true;
        } else if (foodHue <= maxHue + 1 && foodHue >= minHue + 1) {
            canEat = true;
        } else if (foodHue <= maxHue - 1 && foodHue >= minHue - 1) {
            canEat = true;
        } else if (edibleRange >= 1.0) {
            //eat anything
            canEat = true;
        }

        return canEat;
    }

    /**
     * Attempts to mate with another organism.
     * @param organism The organism to mate with.
     */
    public void mate(Organism organism) {
        String myCode = this.getGeneticInformation();
        String theirCode = organism.getGeneticInformation();
        //
        // Process:
        //
        // 1. Pad all of the values to ensure that the bytes line up. 
        //    - This is done automatically by inserting them into the getGeneticCode string as Hex values.
        //    - Note: to simplify, this doesn't flip on single bits, just single hex values (group of 4 bits).
        // 2. Extract genetic data that does not match in both organisms. This is done by gene ID.
        //    - Save the non-matching genes in an array.
        //    - Remove non-matching genes from the code strings.
        //    - Put the matching genes in order by ID.
        //    - This can be done by String.split(",");
        // 3. Remove metainformation from strings.
        //    - "r#" "," etc.
        //    - This can be done by String.split("#") - The 0 value is the id of the component, the 1 value is the value of the component.
        //    - Store the size (in bytes) of the value in order for each component.
        //      - This is used to reassamble the genes. (to know what bits match which component when putting it back together).
        // 4. Flip at a random point, any number of times (the raw strings).
        // 5. Flip random bits by chance. This can be done by changing on hex number to another hex number. It's the same thing as flipping a random bit, but easier.
        // 6. Re-assemble the resulting strings, with the metainformation extracted from (3). 
        // 7. With a 50% chance, tack on the non-matching genetic data from (2) to each result string. The order of the genes is no longer important after the flip.
        // 8. Select a result string at random. Should we use an actual heuristic here to select the "best" child? I don't think so, because the env kills bad genes to begin with. 
        // 9. Create a resulting organism based on the selected String.
        //
        // Required:
        //
        // + Constructor to create organism from genetic code.
        // + ID's that match up cross-organism. There will have to be a master list of genes from which organisms are created.
        //
        ExposedAttribute health = this.getAttribute(Organism.ATTRIBUTE_TYPE.HEALTH);
        double healthPercent = (double) health.getCurrent() / (double) health.getMax();
        if (this.parentSimulation.getOrganisms().size() >= 50 || healthPercent < 0.2) {
//            this.eat(organism);
            return;
        }

//        if (this.getPosition().getXYDistance(((ConcreteOrganism)organism).getPosition()) > 50) {
//            System.out.println(this.getPosition().getXYDistance(((ConcreteOrganism)organism).getPosition()));
//            this.eat(organism); // Just move if my mate isn't that close.
//            return;
//        }

        health.setCurrent((int) (health.getCurrent() - (health.getMax() * 0.20)));

        String mySplitById[] = myCode.split("\\|");       // Transient.
        String theirSplitById[] = theirCode.split("\\|"); // Transient.


        int myId = Integer.valueOf(mySplitById[0]);
        int theirId = Integer.valueOf(theirSplitById[0]);

        String myComponents[] = mySplitById[1].split(",");
        String theirComponents[] = theirSplitById[1].split(",");

        StringBuilder myRawInformation = new StringBuilder();
        StringBuilder theirRawInformation = new StringBuilder();

        ArrayList<GeneticCodeHelper> myHelper = new ArrayList<GeneticCodeHelper>();
        ArrayList<GeneticCodeHelper> theirHelper = new ArrayList<GeneticCodeHelper>();
        ArrayList<GeneticCodeHelper> unmatchedGenes = new ArrayList<GeneticCodeHelper>();

        parsingHelper(myComponents, myRawInformation, myHelper);
        parsingHelper(theirComponents, theirRawInformation, theirHelper);

        // Remove all unmatched genes from both mine and theirs,
        // put them in the unmatched genes list.
        for (GeneticCodeHelper myHelperBuffer : myHelper) {
            boolean matched = false;
            for (GeneticCodeHelper theirHelperBuffer : theirHelper) {
                if (theirHelperBuffer.equals(myHelperBuffer)) {
                    matched = true;
                }
            }
            if (!matched) {
                myHelper.remove(myHelperBuffer);
                unmatchedGenes.add(myHelperBuffer);
            }
        }
        for (GeneticCodeHelper theirHelperBuffer : theirHelper) {
            boolean matched = false;
            for (GeneticCodeHelper myHelperBuffer : myHelper) {
                if (theirHelperBuffer.equals(myHelperBuffer)) {
                    matched = true;
                }
            }
            if (!matched) {
                theirHelper.remove(theirHelperBuffer);
                unmatchedGenes.add(theirHelperBuffer);
            }
        }
        Collections.sort(myHelper);
        Collections.sort(theirHelper);

        for (GeneticCodeHelper helper : myHelper) {
            myRawInformation.append(helper.geneticData);
        }
        for (GeneticCodeHelper helper : theirHelper) {
            theirRawInformation.append(helper.geneticData);
        }

        ArrayList<String> children = new ArrayList<String>();
        int numberOfChildren = 2; // Get this value from the mate overrides. 
        for (int i = 0; i < numberOfChildren; ++i) {
            String child = twist(myRawInformation.toString().trim(), theirRawInformation.toString().trim()).trim();
            children.add(child);
        }

        // Assemble the children into organisms, and place the organisms in the simulation. \

        for (String child : children) {
            child = byteFlip(child).trim();
            String childGeneticInformation = assemble(child, myHelper).trim(); // myHelper is used here only to match id's with lengths when re-assembling the gene. 
            childGeneticInformation = selectUnmatchedGenes(childGeneticInformation, unmatchedGenes);
            String tempGene = childGeneticInformation.substring(childGeneticInformation.indexOf("g#")+2);
            Gene gene = parentSimulation.getMasterlist().getGene(tempGene);
            //don't give birth if there is no gene available
            if(gene!=null){
                parentSimulation.addOrganism(new ConcreteOrganism(childGeneticInformation, parentSimulation), this.getPosition());
            }
        }


        checkLife();
    }

    private String byteFlip(String child) {
        // MAX - TARGET / MAX = CHANCE OF BYTE FLIP.
        // That is, if a random number from 0 to max is greater
        // than or equal to the target, the byte will be flipped.
        final int TARGET = 99; // 1% chance to change raplce a byte with another ransom byte (faster evo than single bit).
        final int MAX = 100;

        char cArray[] = child.toCharArray();
        Random random = new Random();
        for (int i = 0; i < cArray.length; ++i) {
            if (random.nextInt(MAX) >= TARGET) {
                int randomInt = random.nextInt();
                String hexString = Integer.toHexString(i);
                cArray[i] = hexString.charAt(hexString.length() - 1);
               // System.out.println("+++++++++++++ Flipped bit: " + (char) cArray[i]);
            }
        }
        return new String(cArray);
    }

    private String byteToHex(byte data) {
        StringBuffer buf = new StringBuffer();
        buf.append(toHexChar((data >>> 4) & 0x0F));
        buf.append(toHexChar(data & 0x0F));
        return buf.toString().trim();
    }

    private char toHexChar(int i) {
        return ((0 <= i) && (i <= 9)) ? (char) ('0' + i) : (char) ('a' + (i - 10));
    }

    private String selectUnmatchedGenes(String child, ArrayList<GeneticCodeHelper> unmatchedGenes) {
        Random random = new Random();
        StringBuilder builder = new StringBuilder();
        builder.append(child);
        for (GeneticCodeHelper helper : unmatchedGenes) {
            if (random.nextBoolean()) {
                builder.append(helper.getGeneId());
                builder.append("#");
                builder.append(helper.getGeneticData());
                builder.append(",");
            }
        }
        child = builder.toString().trim();
        if(child.endsWith(",")){
            child = child.substring(0, child.length() - 1);
        }
        return  child;// Remove last comma.
    }

    private String assemble(String child, ArrayList<GeneticCodeHelper> metaInformation) {
        StringBuilder builder = new StringBuilder();
        int pos = 0;

        builder.append("-1|"); // The constructor will assign the id. 

        // c# is the first 6 bytes.
        builder.append("c#");
        builder.append(child.substring(pos, pos + 6));
        pos += 6;
        builder.append(",");
        // e# is the next 6 bytes.
        builder.append("e#");
        builder.append(child.substring(pos, pos + 6));
        pos += 6;
        builder.append(",");
        //#g is the remaining sections
        builder.append("g#");
        builder.append(child.substring(pos));
        
        // r# is the next 16 bytes.
//        builder.append("r#");
//        builder.append(child.substring(pos, pos+16));
//        pos += 16;
//        builder.append(",");

//        for (GeneticCodeHelper helper : metaInformation) {
//            builder.append(helper.getGeneId());
//            builder.append("#");
//            builder.append(child.substring(pos, pos + (helper.getByteSize())));
//            builder.append(",");
//            pos += helper.getByteSize();
//        }
        String returnCode = builder.toString().trim();
        return returnCode;
    }

    // Performs the actual crossover. 
    private String twist(String myCode, String theirCode) {
        String code = "";
        if (myCode.length() == theirCode.length()) {
            Random random = new Random();
            int crossOverPoint = random.nextInt(myCode.length());
            code = myCode.substring(0, crossOverPoint) + theirCode.substring(crossOverPoint, theirCode.length());
        } else {
            System.err.println("ERROR: length of parent genes are not equal, can not cross. Cloning parent.");
            code = myCode;
        }
        return code.trim();
    }

    /**
     * Extract ID, size of byte array, and data.
     * After their components have been parsed, 
     * pop elements that are only in one list 
     * into a buffer.
     * Then, order both arrays, and push them into the raw string.
     */
    private void parsingHelper(String components[], StringBuilder builder, ArrayList<GeneticCodeHelper> helper) {
        for (String component : components) {
            String keyValuePairs[] = component.split("\\#");
            String key = keyValuePairs[0];
            String value = keyValuePairs[1];
            /*
             * Mandatory fields.
             */
            if ("c".equals(key) || "e".equals(key) || "r".equals(key) || "g".equals(key)) {
                builder.append(value.trim());
            } else {
                int geneId = Integer.valueOf(key);
                helper.add(new GeneticCodeHelper(geneId, value.trim()));
            }
        }
    }

    public Simulation getParentSimulation() {
        return parentSimulation;
    }

    public void setParentSimulation(Simulation parentSimulation) {
        this.parentSimulation = parentSimulation;
    }

    private class GeneticCodeHelper implements Comparable {

        private Integer geneId = -1;
        private String geneticData = null;

        public GeneticCodeHelper(int geneId, String geneticData) {
            this.geneId = geneId;
            this.geneticData = geneticData;
        }

        public int compareTo(Object o) {
            return this.geneId.compareTo(((GeneticCodeHelper) o).geneId);
        }

        public int getGeneId() {
            return geneId;
        }

        public void setGeneId(int geneId) {
            this.geneId = geneId;
        }

        public String getGeneticData() {
            return geneticData;
        }

        public void setGeneticData(String geneticData) {
            this.geneticData = geneticData;
        }

        @Override
        public boolean equals(Object o) {
            return this.geneId.equals(((GeneticCodeHelper) o).geneId);
        }

        public int getByteSize() {
            return geneticData.length();
        }
    }

    /**
     * Causes the organism to attempt to move.
     * @param parent The simulation environment.
     */
    public Position move(Simulation parent, ConcreteOrganism organism) {
        ArrayList<Position> votes = new ArrayList<Position>();

        for (Gene gene : genome) {
            votes.add(gene.move(parent, this));
        }
        this.direction = votes.get(0);  // Replace this with an average vector from all votes.


        // Use normalized direction vector scaled by organism speed to change current position:
        Position moveTo = new Position(0, 0, 0);
        moveTo.setX(this.getDirection().getX() * this.getAttribute(Organism.ATTRIBUTE_TYPE.FORTITUDE).getCurrent() + position.getX());
        moveTo.setY(this.getDirection().getY() * this.getAttribute(Organism.ATTRIBUTE_TYPE.FORTITUDE).getCurrent() + position.getY());
        moveTo.setZ(this.getDirection().getZ() * this.getAttribute(Organism.ATTRIBUTE_TYPE.FORTITUDE).getCurrent() + position.getZ());

        if (parent.locationAvailable(moveTo, getSize(), this)) {
            position = moveTo;
        }

        checkLife();
        return votes.get(0); // Replace this with an average vector from all votes.
    }

    /**
     * Saves the organism data to file.
     * @param parent
     */
    public void save(Element parentElm, Document doc) {
        Element org = doc.createElement("organism");

        Element savId = doc.createElement("id");
        savId.appendChild(doc.createTextNode("" + myId));
        org.appendChild(savId);

        Element savAlive = doc.createElement("alive");
        savAlive.appendChild(doc.createTextNode("" + alive));
        org.appendChild(savAlive);

        Element savCol = doc.createElement("color");
        Element colRed = doc.createElement("red");
        Element colGrn = doc.createElement("green");
        Element colBlu = doc.createElement("blue");
        colRed.appendChild(doc.createTextNode("" + color.getRed()));
        colGrn.appendChild(doc.createTextNode("" + color.getGreen()));
        colBlu.appendChild(doc.createTextNode("" + color.getBlue()));
        savCol.appendChild(colRed);
        savCol.appendChild(colGrn);
        savCol.appendChild(colBlu);
        org.appendChild(savCol);

        Element savEatCol = doc.createElement("eatColor");
        Element eatRed = doc.createElement("red");
        Element eatGrn = doc.createElement("green");
        Element eatBlu = doc.createElement("blue");
        eatRed.appendChild(doc.createTextNode("" + edibleFoodColor.getRed()));
        eatGrn.appendChild(doc.createTextNode("" + edibleFoodColor.getGreen()));
        eatBlu.appendChild(doc.createTextNode("" + edibleFoodColor.getBlue()));
        savEatCol.appendChild(eatRed);
        savEatCol.appendChild(eatGrn);
        savEatCol.appendChild(eatBlu);
        org.appendChild(savEatCol);

        Element savPosn = doc.createElement("position");
        Element posx = doc.createElement("x");
        Element posy = doc.createElement("y");
        Element posz = doc.createElement("z");
        posx.appendChild(doc.createTextNode("" + position.getX()));
        posy.appendChild(doc.createTextNode("" + position.getY()));
        posz.appendChild(doc.createTextNode("" + position.getZ()));
        savPosn.appendChild(posx);
        savPosn.appendChild(posy);
        savPosn.appendChild(posz);
        org.appendChild(savPosn);

        Element savDir = doc.createElement("direction");
        Element dirx = doc.createElement("x");
        Element diry = doc.createElement("y");
        Element dirz = doc.createElement("z");
        dirx.appendChild(doc.createTextNode("" + direction.getX()));
        diry.appendChild(doc.createTextNode("" + direction.getY()));
        dirz.appendChild(doc.createTextNode("" + direction.getZ()));
        savDir.appendChild(dirx);
        savDir.appendChild(diry);
        savDir.appendChild(dirz);
        org.appendChild(savDir);

        Element outAttr = doc.createElement("attributes");
        for (int i = 0; i < attributes.length; i++) {
            ExposedAttribute myatr = attributes[i];
            myatr.save(outAttr, doc);
        }
        org.appendChild(outAttr);

        Element savGenome = doc.createElement("genome");
        for (int i = 0; i < genome.size(); i++) {
            Gene gene = genome.get(i);
            gene.save(savGenome, doc);
        }
        org.appendChild(savGenome);

        parentElm.appendChild(org);
    }

    public void load(Element input) {
        try {
            Element curorg = input;
            //id, alive, genome, color, edibleFoodColor
            myId = Integer.parseInt(curorg.getElementsByTagName("id").item(0).getTextContent());
            if (curorg.getElementsByTagName("alive").item(0).getTextContent().equals("true")) {
                alive = true;
            } else {
                alive = false;
            }
            color = getXMLColor((Element) (curorg.getElementsByTagName("color").item(0)));
            edibleFoodColor = getXMLColor((Element) (curorg.getElementsByTagName("eatColor").item(0)));

            NodeList loadPos = curorg.getElementsByTagName("position").item(0).getChildNodes();
            for (int j = 0; j < loadPos.getLength(); j++) {
                if (loadPos.item(j).getNodeName().equals("x")) {
                    position.setX(Double.parseDouble(loadPos.item(j).getTextContent()));
                } else if (loadPos.item(j).getNodeName().equals("y")) {
                    position.setY(Double.parseDouble(loadPos.item(j).getTextContent()));
                } else if (loadPos.item(j).getNodeName().equals("z")) {
                    position.setZ(Double.parseDouble(loadPos.item(j).getTextContent()));
                }
            }

            NodeList loadDir = curorg.getElementsByTagName("direction").item(0).getChildNodes();
            for (int j = 0; j < loadPos.getLength(); j++) {
                if (loadDir.item(j).getNodeName().equals("x")) {
                    direction.setX(Double.parseDouble(loadDir.item(j).getTextContent()));
                } else if (loadDir.item(j).getNodeName().equals("y")) {
                    direction.setY(Double.parseDouble(loadDir.item(j).getTextContent()));
                } else if (loadDir.item(j).getNodeName().equals("z")) {
                    direction.setZ(Double.parseDouble(loadDir.item(j).getTextContent()));
                }
            }

            NodeList traits = curorg.getElementsByTagName("genome").item(0).getChildNodes();
            for (int j = 0; j < traits.getLength(); j++) {
                if (traits.item(j).getNodeName().equals("gene")) {
                    ConcreteGene loadGene = new ConcreteGene(this);
                    loadGene.load((Element) (traits.item(j))); //Load this gene
                    genome.add(loadGene);
                }
            }

            NodeList attrList = curorg.getElementsByTagName("attributes").item(0).getChildNodes();
            int atrcount = 0;
            for (int j = 0; j < attrList.getLength(); j++) {
                if (attrList.item(j).getNodeName().equals("atr")) {
                    attributes[atrcount].load((Element) (attrList.item(j)));
                    atrcount++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace(); //A bug occured
        }
    }

    private Color getXMLColor(Element colelm) {
        Color retval = Color.white;
        try {
            NodeList red = colelm.getElementsByTagName("red");
            NodeList green = colelm.getElementsByTagName("green");
            NodeList blue = colelm.getElementsByTagName("blue");

            int rval = Integer.parseInt(red.item(0).getTextContent());
            int gval = Integer.parseInt(green.item(0).getTextContent());
            int bval = Integer.parseInt(blue.item(0).getTextContent());
            retval = new Color(rval, gval, bval);
        } catch (Exception e) {
        }
        return retval;
    }

    /**
     * Returns the value of the requested attribute
     * @param attributeType - the attribute requested (i.e. age, brawn, etc.)
     * @return the value of this organism's selected attribute
     */
    public ExposedAttribute getAttribute(ATTRIBUTE_TYPE attributeType) {
        return attributes[attributeType.ordinal()];
    }

    /**
     * Set an attribute value
     * @param attributeType - the attribute requested (i.e. age, brawn, etc.)
     * @param newCurrentValue - the value the attribute will be set to
     */
    public void setAttributeValue(ATTRIBUTE_TYPE attributeType, int newCurrentValue) {
        attributes[attributeType.ordinal()].setCurrent(newCurrentValue);
        checkLife();
    }

    /**
     * Get the position of the organism in the environment
     * @return the position of the organism in the environment
     */
    public Position getPosition() {
        return position;
    }

    /**
     * Set the position of the organism in the environment
     * @param position - the coordinates of the organism in the environment
     */
    public void setPosition(Position position) {
        this.position = position;
    }

    public ArrayList<Gene> getGenome() {
        return genome;
    }

    /**
     * @return the value of this organism's selected attribute
     */
    public ExposedAttribute getAttribute(int ordinal) {
        return attributes[ordinal];
    }

    /**
     * 
     * @param toParse
     * @return
     */
    public int getAttributeOrdinalFromString(String toParse) {
        ATTRIBUTE_TYPE valueOf = ATTRIBUTE_TYPE.valueOf(toParse);

        return valueOf.ordinal();
    }

    /**
     * Sets the ExposedAttribute at the given location
     */
    public void setAttribute(int ordinal, ExposedAttribute newValue) {
        this.attributes[ordinal] = newValue;
        checkLife();
    }

    /**
     * Returns an ACTION_TYPE from an ordinal value
     * @param ordinal
     * @return
     */
    public ACTION_TYPE getActionType(int ordinal) {
        return ACTION_TYPE.values()[ordinal];
    }

    /**
     * 
     * @param toParse
     * @return
     */
    public int getActionOrdinalFromString(String toParse) {
        ACTION_TYPE valueOf = ACTION_TYPE.valueOf(toParse);

        return valueOf.ordinal();
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
        float[] myHSBColor = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
        float newHue = myHSBColor[0] + (float) .333;  //offset preferred color by 1/3 of the wheel
        if (newHue > 1.0) {
            newHue -= 1.0;
        }
        this.edibleFoodColor = Color.getHSBColor(newHue, (float) .5, (float) .5);
        System.out.println("I can eat: "+(newHue*359));
    }

    public Color getEdibleFoodColor() {
        return edibleFoodColor;
    }

    public Position getDirection() {
        return direction;
    }

    public void setDirection(Position direction) {
        this.direction = normalize(direction);
    }

    private Position normalize(Position direction) {
        double x = direction.getX();
        double y = direction.getY();
        double z = direction.getZ();

        double length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));

        direction.setX(x / length);
        direction.setY(y / length);
        direction.setZ(z / length);

        return direction;
    }

    /**
     * Determines if the Organism is still alive or not
     */
    public boolean checkLife() {
        int currentHP = getAttribute(Organism.ATTRIBUTE_TYPE.HEALTH).getCurrent();

        if (currentHP > 0 && alive) {
            //still alive
            alive = true;
        } else if (alive) {
            //Set back to full health, but kill
            alive = false;
            attributes[Organism.ATTRIBUTE_TYPE.HEALTH.ordinal()].setCurrent(
                    attributes[Organism.ATTRIBUTE_TYPE.HEALTH.ordinal()].getMax());
        }

        return alive;
    }

    /**
     * 
     * @return true if this organism should still exist in the environment, false if not
     */
    public boolean exists() {
        boolean stillExists = true;
        int currentHP = getAttribute(Organism.ATTRIBUTE_TYPE.HEALTH).getCurrent();

        if (!alive && currentHP <= 0) {
            stillExists = false;
        }

        return stillExists;
    }

    /**
     * Determine the radius of this organism
     * @return - the radial size of the organism
     */
    public double getSize() {
        return (this.getAttribute(Organism.ATTRIBUTE_TYPE.BRAWN).getCurrent() / 2.0) + 1.0;
    }

    /**
     * getHue - returns the Hue (from an HSB conversion of the color)
     * @return - the Hue value of this organism's color
     */
    public double getHue() {
        float[] myHSBColor = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);

        return myHSBColor[0];
    }

    /** Get the distance from this organism to another
     * @param other The other organism to compare against
     * @return The total distance between us.
     **/
    public double getDistance(ConcreteOrganism other) {
        double retval = 0;
        retval = this.position.getXYDistance(other.getPosition());
        return retval;
    }

    /**
     * Determines if a Color is part 
     * @param testColor
     * @return
     */
    public boolean isInColorFamily(Color testColor) {
        float[] testColorFloats = Color.RGBtoHSB(testColor.getRed(), testColor.getGreen(), testColor.getBlue(), null);
        double myHue = this.getHue();
        double maxHue = myHue + .15;
        double minHue = myHue - .15;
        boolean inFamily = false;

        if (testColorFloats[0] <= maxHue && testColorFloats[0] >= minHue) {
            inFamily = true;
        } else if (testColorFloats[0] <= maxHue + 1 && testColorFloats[0] >= minHue + 1) {
            inFamily = true;
        } else if (testColorFloats[0] <= maxHue - 1 && testColorFloats[0] >= minHue - 1) {
            inFamily = true;
        }

        return inFamily;
    }

    /********* ---Start of Tree Node functions--- *******/
    // WARNING: It is not a good idea to use these functions. They're here primarily for the the trait tree to work.
    @Override
    public String toString() {
        return "Organism " + this.myId;
    }

    /**
     * Returns the child <code>TreeNode</code> at index 
     * <code>childIndex</code>.
     */
    public TreeNode getChildAt(int childIndex) {
        return genome.get(childIndex);
    }

    /**
     * Returns the index of <code>node</code> in the receivers children.
     * If the receiver does not contain <code>node</code>, -1 will be
     * returned.
     */
    public int getIndex(TreeNode node) {
        return genome.indexOf(node);
    }

    /**
     * Returns true if the receiver is a leaf.
     */
    public boolean isLeaf() {
        return genome.size() == 0;
    }

    /** Insert a node into the tree **/
    public void insert(MutableTreeNode child, int index) {
        genome.add(index, (Gene) child);
    }

    public void remove(int index) {
        genome.remove(index);
    }

    public void remove(MutableTreeNode node) {
        genome.remove(node);
    }

    public void removeFromParent() {
        parentSimulation.getMasterlist().remove(this); //Remove ourselves, for no specific reason.
    }

    public void setParent(MutableTreeNode newParent) {
    //Not supported
    }

    public void setUserObject(Object object) {
    //Unsupported
    }

    public class Enumdapter implements Enumeration {

        Iterator iter; //Internal iterator

        /** Creates a new instance of Enumdapter */
        public Enumdapter(Iterator myit) {
            iter = myit;
        }

        public Object nextElement() {
            return iter.next();
        }

        public boolean hasMoreElements() {
            return iter.hasNext();
        }
    }

    public Enumeration children() {
        return new Enumdapter(genome.iterator());
    }

    public boolean getAllowsChildren() {
        return true;
    }

    public int getChildCount() {
        return genome.size();
    }

    public TreeNode getParent() {
        return parentSimulation.getMasterlist();
    }
}
